/*
 * Copyright (c) 2000-2015 Tacke Consulting, Inc
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions 
 * of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using OpenNETCF.Media.DirectShow;
using System.Reflection;
using OpenNETCF.Win32;
using System.IO;

namespace OpenNETCF.Media
{    
    /// <summary>
    /// Direct Show player control for Windows CE devices.
    /// </summary>
    public partial class DirectShowPlayerControl : BaseMediaPlayer
    {
        private IGraphBuilder m_graphBuilder;
        private IVideoWindow m_videoWindow;
        private DShowGraph m_graph;
        private bool m_autoSize = false;
        private bool m_audioOnly = false;

        internal static DebugLogger m_logger = new DebugLogger("\\MediaPlayerLog.txt", true, true);

        internal override DebugLogger Logger
        {
            get
            {
                return m_logger;
            }
        }

        /// <summary>
        /// Default contructor
        /// </summary>
        public DirectShowPlayerControl()
        {
            Logger.WriteLine("+DirectShowPlayerControl");

            if (!COMHelper.IsComObjectRegistered(COMHelper.CLSID_FILTERGRAPH))
            {
                Logger.WriteLine("+Filtergraph CLSID not registered");
                throw new DirectShowNotRegisteredException();
            }

            InitializeComponent();
            Logger.WriteLine("-DirectShowPlayerControl");
        }

        public Size GetVideoNativeSize()
        {
            if ((m_graphBuilder == null) || (m_audioOnly)) return new Size(0, 0);

            try
            {
                IBasicVideo bv = (IBasicVideo)m_graphBuilder;
                int height, width;
                bv.GetVideoSize(out width, out height);

                return new Size(width, height);
            }
            catch (Exception e)
            {
                MediaPlayerException mpe = new ExceptionEventArgs(new DirectShowException(e)).MediaException;
                Logger.WriteException("Exception getting native size of media file.", mpe);
                throw mpe;
            }
        }

        public override double SegmentLength
        {
            get { throw new NotSupportedException("SegmentLength not supported under DirectShow"); }
            set { throw new NotSupportedException("SegmentLength not supported under DirectShow"); }
        }

        public bool Autosize
        {
            get { return m_autoSize; }
            set { m_autoSize = value; }
        }

        private void Open(string mediaFile)
        {
            Logger.WriteLine(string.Format("Opening media file '{0}'", mediaFile));

            if (m_graph != null)
            {
                Logger.WriteLine("Graph already exists");

                if (MediaState != MediaState.Stopped)
                {
                    Logger.WriteLine("Stopping existing graph");
                    m_graph.StopWhenReady();
                }

                //Unit from the graph
                Logger.WriteLine("Clean up existing graph");
                this.Uninitialize();
            }
            try
            {
                m_audioOnly = false;

                Logger.WriteLine("Creating new graph");
                m_graph = new DShowGraph(mediaFile);
                m_graph.ProgressChanged += new OpenNETCF.Media.DirectShow.ProgressChangedEventHandler(graph_ProgressChanged);
                m_graph.ConversionComplete += new GraphCompletedEventHandler(graph_ConversionComplete);


                Logger.WriteLine(" Calling m_graph.Open");
                m_graphBuilder = m_graph.Open(Logger);

                Application.DoEvents();
            }
            catch (Exception e)
            {
                m_graph.Dispose();
                m_graph = null;
                MediaPlayerException mpe = new ExceptionEventArgs(new DirectShowException(e)).MediaException;
                Logger.WriteException("Exception while trying to open media file.", mpe);
                throw mpe;
            }

            Logger.WriteLine(" Calling m_graph.GetVideoWindow");
            m_videoWindow = (IVideoWindow)m_graph.GetVideoWindow();

            if (m_videoWindow == null)
            {
                throw new PlatformNotSupportedException("Unable to create an IVideoWindow instance (CLSID {56a868b4-0ad4-11ce-b03a-0020af0ba770})");
            }

            Logger.WriteLine(" Setting window style");
            try
            {
                m_videoWindow.put_WindowStyle((int)(WS.CHILD | WS.VISIBLE | WS.CLIPSIBLINGS));
            }
            catch (InvalidCastException)
            {
                Logger.Write(" Assuming no video rendering (audio-only)");
                m_audioOnly = true;
            }

            if (!m_audioOnly)
            {
                try
                {
                    //Set the size
                    if (Autosize)
                    {
                        Logger.WriteLine(" Autosizing");
                        this.Size = new Size(m_videoWindow.get_Width(), m_videoWindow.get_Height());
                    }

                    Logger.WriteLine(" Setting internal bounds");
                    SetMediaPlayerBoundsInternal(this.Bounds);

                    Logger.WriteLine(" Parenting");
                    m_videoWindow.put_Owner(Handle);
                    Logger.WriteLine(" Hooking drain");
                    m_videoWindow.put_MessageDrain(Handle);
                }
                catch (Exception e)
                {
                    MediaPlayerException mpe = new ExceptionEventArgs(new DirectShowException(e)).MediaException;
                    Logger.WriteException("Exception while trying to open media file.", mpe);
                    throw mpe;
                }
            }

            //Raise the event
            Logger.WriteLine(" Calling OnMediaOpened");
            OnMediaOpened(m_graph.Duration);

            Logger.WriteLine("-Open");
        }

        public override void Close()
        {
            Uninitialize();
        }

        internal override void OnMediaOpened(double duration)
        {
            Logger.WriteLine(string.Format("+DShow.OnMediaOpened({0})", duration));

            TimeSpan ts;

            if (duration < 60)
            {
                ts = new TimeSpan(0, 0, (int)duration);
            }
            else
            {
                Logger.WriteLine(" Calling DateTime.FromFileTimeUtc");
                DateTime t = DateTime.FromFileTimeUtc((long)duration);
                Logger.WriteLine(" Creating TimeSpan");


                try
                {
                    ts = new TimeSpan(0, t.Hour, t.Minute, t.Second, 0);
                }
                catch (Exception ex)
                {
                    Logger.WriteException(string.Format("Unable to create a timespan from a DateTime value of [{0}]", t.ToString("HH:mm:ss.fff")), ex);
                    ts = new TimeSpan(0);
                }
            }

            Logger.WriteLine(" Calling base.OnMediaOpened");
            base.OnMediaOpened(ts.TotalSeconds);
            Logger.WriteLine("-DShow.OnMediaOpened");
        }

        void graph_ConversionComplete(object sender, ConversionCompletedEventArgs e)
        {
            MediaStateChangedEventArgs state = new MediaStateChangedEventArgs(MediaState.Stopped);

            if (e.Error != null)
            {
                //There was an error so raise the error event then the stop event
                OnMediaPlaybackError(state, new ExceptionEventArgs(e.Error));
            }

            OnMediaStateChanged(state);
        }

        void graph_ProgressChanged(object sender, OpenNETCF.Media.DirectShow.ProgressChangedEventArgs e)
        {
            base.OnMediaPositionChanged(e.Duration, e.Position);
        }

        internal override void PlayInternal()
        {
            if (string.IsNullOrEmpty(MediaUrl)) return;
            if (m_graph == null) return;

            Logger.WriteLine("+PlayInternal");

            try
            {
                if (m_graph.State == FilterState.Running) return;

                if (MediaState == MediaState.Paused)
                {
                    //If we are paused continue playing
                    Logger.WriteLine(" Calling m_graph.Play...");
                    m_graph.Play();
                }
                else
                {
                    //Set the start time
                    Logger.WriteLine(" Calling m_graph.SeekTo...");
                    m_graph.SeekTo((ulong)m_startTime);

                    //Run Async
                    Logger.WriteLine(" Calling m_graph.RunAsync...");
                    m_graph.RunAsync();
                }
            }
            catch (Exception e)
            {
                MediaPlayerException mpe = new ExceptionEventArgs(new DirectShowException(e)).MediaException;
                m_logger.WriteException("Exception while trying to play media file.", mpe);
                throw mpe;
            }
            finally
            {
                Logger.WriteLine("-PlayInternal");
            }
        }

        internal override void PauseInternal()
        {
            if (m_graph == null) return;
            if (m_graph.State != FilterState.Running) return;

            m_graph.Pause();
        }

        internal override void StopInternal()
        {
            if (m_graph == null) return;
            if (m_graph.State == FilterState.Stopped) return;

            m_graph.CancelAsync();
            m_graph.Stop();
        }

        internal override void Initialize()
        {
            //nothing to do
        }

        internal override void SetMediaPlayerBoundsInternal(Rectangle bounds)
        {
            if (m_graph == null) return;

            if ((m_videoWindow != null) && (!m_audioOnly))
            {
                try
                {
                    m_videoWindow.SetWindowPosition(0, 0, bounds.Width, bounds.Height);
                }
                catch (Exception e)
                {
                    if (e.Message != "0x80004002") //0x80004002 is No Such Interface Supported (E_NOINTERFACE) meaning the window is not ready to be sized
                    {
                        throw;
                    }
                }
            }
        }

        internal override Bitmap DesignerBackgroundImage
        {
            get { return OpenNETCF.Media.Properties.Resources.DShow; }
        }

        /// <summary>
        /// Gets the DShowGraph object for direct manipulation to Direct Show
        /// </summary>
        internal DShowGraph DirectShowGraph
        {
            get { return m_graph; }
        }

        /// <summary>
        /// Gets or sets whether the media file should start playing immediately after it is opened.
        /// </summary>
        public override bool AutoPlay
        {
            get { return m_autoPlay; }
            set { m_autoPlay = value; }
        }

        /// <summary>
        /// Gets or sets the location within the media file (in seconds) to start playing from.
        /// </summary>
        public override double StartTime
        {
            get
            {
                DateTime dt = DateTime.FromFileTimeUtc((long)m_startTime);
                TimeSpan ts = new TimeSpan(0, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
                return ts.TotalSeconds;
            }
            set
            {
                if (m_graph == null)
                    return;
                //Convert to UTC
                TimeSpan ts = new TimeSpan(0, 0, 0, (int)value);
                double startTime = new DateTime(1601, 01, 01, ts.Hours, ts.Minutes, ts.Seconds).ToFileTimeUtc();
                if (startTime < m_graph.Duration)
                {
                    m_startTime = startTime;
                    if (MediaState == MediaState.Playing || MediaState == MediaState.Paused)
                        m_graph.SeekTo((ulong)m_startTime);
                }
            }
        }

        /// <summary>
        /// Gets the duration of the media
        /// <remarks>The duration cannot be determined until after the MediaOpened event has occured.  MediaOpened event is triggered by the MediaUrl property.</remarks>
        /// </summary>
        public override double Duration
        {
            get
            {
                if (IsDesignTime)
                {
                    return 0;
                }

                if (m_graph == null)
                {
                    return 0;
                }

                return m_graph.Duration;
            }
        }

        /// <summary>
        /// Gets or sets a value to set the media player to full screen
        /// </summary>
        public override bool FullScreen
        {
            get
            {
                return m_fullscreen;
            }
            set
            {
                m_fullscreen = value;
                SetFullScreen();
            }
        }

        private void SetFullScreen()
        {
            if ((IsDesignTime) || (m_graph == null) || (m_videoWindow == null))
            {
                return;
            }

            if (!m_audioOnly)
            {
                try
                {
                    m_videoWindow.put_FullScreenMode(FullScreen ? -1 : 0);
                }
                catch 
                { 
                    m_audioOnly = true;
                    return;
                }
            }

            if (!FullScreen)
            {
                SetMediaPlayerBoundsInternal(this.Bounds);
            }
        }

        /// <summary>
        /// Gets the current state of the media associated with the media player
        /// </summary>
        public override MediaState MediaState
        {
            get
            {
                if (m_graph == null)
                {
                    return MediaState.Stopped;
                }

                return FilterStateToMediaState(m_graph.State);
            }
        }

        /// <summary>
        /// Gets or sets the URL to a media source. The media source can be of any type supported by the Direct Show Player.
        /// </summary>
        /// <exception cref="MediaPlayerException">Will throw a MediaPlayerException if there is an error openning the media file.</exception>
        public override string MediaUrl
        {
            get
            {
                return m_mediaFile;
            }
            set
            {
                if (m_mediaFile != value || value != null)
                {
                    if (value.Length != 0)
                    {
                        m_mediaFile = value;

                        if (!IsDesignTime)
                        {
                            //Open the media file
                            Open(m_mediaFile);

                            if (m_autoPlay)
                            {
                                Play();
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the current position of the media file.
        /// </summary>
        /// <remarks>
        /// Setting the current position is not supported by DirectShow
        /// </remarks>
        public override double Position
        {
            get
            {
                if (IsDesignTime || m_graph == null)
                {
                    return 0;
                }

                return (double)m_graph.Position;
            }
            set
            {
                if (IsDesignTime || m_graph == null)
                {
                    return;
                }
                m_graph.SeekTo((ulong)value);
            }
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }

            //Stop the media incase it's playing
            this.StopInternal();

            //Uninitialize everything
            Uninitialize();

            base.Dispose(disposing);
        }

        private void Uninitialize()
        {
            if (m_graph == null)
            {
                return;
            }

            m_graph.CancelAsync();
            System.Threading.Thread.Sleep(1000);

            //Unwire the events
            m_graph.ProgressChanged -= new OpenNETCF.Media.DirectShow.ProgressChangedEventHandler(graph_ProgressChanged);
            m_graph.ConversionComplete -= new GraphCompletedEventHandler(graph_ConversionComplete);

            if (!m_audioOnly)
            {
                IVideoWindow wnd = (IVideoWindow)m_graph.GetVideoWindow();
                if (wnd != null)
                {
                    wnd.put_Owner(IntPtr.Zero);
                    wnd.put_MessageDrain(IntPtr.Zero);
                }
            }

            m_graph.Dispose();
            m_graph = null;
        }

        private MediaState FilterStateToMediaState(FilterState fs)
        {
            switch (fs)
            {
                case FilterState.Paused:
                    return MediaState.Paused;
                case FilterState.Running:
                    return MediaState.Playing;
                case FilterState.Stopped:
                    return MediaState.Stopped;
                default:
                    return MediaState.Unknown;
            }
        }

        private FilterState MediaStateToFilterState(MediaState ms)
        {
            switch (ms)
            {
                case MediaState.Paused:
                    return FilterState.Paused;
                case MediaState.Playing:
                    return FilterState.Running;
                case MediaState.Stopped:
                    return FilterState.Stopped;
                default:
                    return FilterState.Stopped;
            }
        }

    }
}
